/*
 * kylin-os-manager
 *
 * Copyright (C) 2022, KylinSoft Co., Ltd.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include <QDebug>
#include <QProcess>
#include <QStringList>
#include <QDir>
#include <QFile>

#include "collection.h"

namespace
{
constexpr char crashSavePath[] = "/tmp/crash-collection/";
}

namespace crash_collection
{

namespace collection_field
{
constexpr char pid[] = "PID:";
constexpr char signal[] = "Signal:";
constexpr char time[] = "Timestamp:";
constexpr char commandLine[] = "Command Line:";
constexpr char executable[] = "Executable:";
constexpr char stackTrace[] = "Message:";
constexpr char stackTraceThradTitle[] = "Stack trace of thread";
constexpr char systemArch[] = "System Arch:";
constexpr char packageName[] = "Package Name:";
constexpr char packageDepend[] = "Package Depend:";
constexpr char packageVersion[] = "Package Version:";
} // namespace collection_field

Collection::Collection()
{
    connect(&m_watcher, &Watcher::crashPid, this, &Collection::collectionCrashInfo);
}

Collection::~Collection() {}

void Collection::collectionCrashInfo(pid_t pid)
{
    qDebug() << "crash process pid is " << pid;
    if (!collectionByBash(pid)) {
        qWarning() << "bash collection info fail";
    }

    if (!generateReport(pid)) {
        qCritical() << "generate report fail!";
    }

    return;
}

bool Collection::collectionByBash(pid_t pid)
{
    QString command =
        "bash /usr/bin/collection_bash.sh" + QString(" ") + QString::number(pid) + QString(" ") + crashSavePath;
    if (system(command.toLocal8Bit().data())) {
        return false;
    }

    return true;
}

bool Collection::generateReport(pid_t pid)
{
    CrashInfo crashInfo;

    QString reportName = QString::number(pid) + "_crash_report";
    //    QString reportFilePath = getenv("HOME") + QString("/桌面/") + reportName;
    QString reportFilePath = QString("/tmp/") + reportName;

    QString coreFilePath;
    QString infoFilePath;
    QDir dir(crashSavePath + QString::number(pid));
    QStringList fileList = dir.entryList();
    for (int i = 0; i < fileList.size(); i++) {
        if (fileList.at(i) == QString(".") || fileList.at(i) == QString("..")) {
            ;
        } else if (fileList.at(i).contains("core.xz")) {
            coreFilePath = dir.absoluteFilePath(fileList.at(i));
        } else {
            infoFilePath = dir.absoluteFilePath(fileList.at(i));
        }
    }

    if (!analyzeInfoFile(infoFilePath, crashInfo)) {
        qCritical() << "analyze crash info file fail!";
        return false;
    }

    // if (!analyzeCoreFile(reportFilePath , coreFilePath)) {
    //     qCritical() << "analyze crash core file fail!";
    //     return false;
    // }

    if (!generate(reportFilePath, crashInfo)) {
        qCritical() << "generate report fail !";
        return false;
    }

    Q_EMIT collected(reportFilePath);

    return true;
}

bool Collection::generate(QString reportFilePath, CrashInfo &crashInfo)
{
    if (reportFilePath.isEmpty()) {
        qCritical() << "reportFilePath is empty!";
        return false;
    }

    QFile file(reportFilePath);
    if (!file.open(QIODevice::WriteOnly)) {
        qCritical() << "open report file fail!";
        return false;
    }

    QByteArray report;

    /* 生成 pid */
    file.write(collection_field::pid);
    file.write(crashInfo.getPid());
    file.write("\n");

    /* 生成系统架构 */
    file.write(collection_field::systemArch);
    file.write(crashInfo.getArch());
    file.write("\n");

    /* 生成信号 */
    file.write(collection_field::signal);
    file.write(crashInfo.getSignal());
    file.write("\n");

    /* 生成命令行 */
    file.write(collection_field::commandLine);
    file.write(crashInfo.getCmd());
    file.write("\n");

    /* 生成可执行文件路径 */
    file.write(collection_field::executable);
    file.write(crashInfo.getBinPath());
    file.write("\n");

    /* 生成崩溃时间 */
    file.write(collection_field::time);
    file.write(crashInfo.getTime());
    file.write("\n");

    /* 生成包名 */
    file.write(collection_field::packageName);
    file.write(crashInfo.getPackageName());
    file.write("\n");

    /* 生成包依赖 */
    file.write(collection_field::packageDepend);
    file.write(crashInfo.getPackageDepends());
    file.write("\n");

    /* 生成backtrace */
    file.write(crashInfo.getBackTrace());
    file.write("\n");

    file.flush();
    file.close();

    return true;
}

bool Collection::analyzeCoreFile(QString reportFilePath, QString coreFilePath)
{
    qDebug() << "++" << reportFilePath << "--" << coreFilePath;

    if (reportFilePath.isEmpty() || coreFilePath.isEmpty()) {
        return false;
    }

    QFile reportFile(reportFilePath);
    if (!reportFile.open(QIODevice::Append)) {
        qCritical() << "open report file fail!";
        return false;
    }

    QFile coreFile(coreFilePath);
    if (!coreFile.open(QIODevice::ReadOnly)) {
        qCritical() << "open core file fail";
        return false;
    }

    reportFile.write("core dump for base64:");
    reportFile.write("\n");

    int readSize = 0;
    char buf[1024];
    memset(buf, '\0', sizeof(buf));
    while ((readSize = coreFile.read(buf, sizeof(buf))) > 0) {
        QByteArray temp = QByteArray(buf, readSize);
        reportFile.write(temp.toBase64());
        reportFile.write("\n");
        memset(buf, '\0', sizeof(buf));
    }

    coreFile.close();
    reportFile.close();

    return true;
}

bool Collection::analyzeInfoFile(QString infoFilePath, CrashInfo &crashInfo)
{
    qDebug() << "call analyzeInfoFile";

    if (infoFilePath.isEmpty()) {
        return false;
    }

    QFile infoFile(infoFilePath);
    if (!infoFile.open(QIODevice::ReadOnly)) {
        qCritical() << "info file open fail";
        return false;
    }

    QByteArray backTrace;
    QByteArray packageDepends;
    char lineData[1024];
    memset(lineData, '\0', sizeof(lineData));
    while (infoFile.readLine(lineData, sizeof(lineData)) != -1) {
        QString item = QString(lineData).trimmed();
        memset(lineData, '\0', sizeof(lineData));

        if (item.isEmpty()) {
            continue;
        }

        if (item.contains(collection_field::pid)) {
            crashInfo.setPid(getFieldValue(item));
        } else if (item.contains(collection_field::signal)) {
            crashInfo.setSignal(getFieldValue(item));
        } else if (item.contains(collection_field::executable)) {
            crashInfo.setBinPath(getFieldValue(item));
        } else if (item.contains(collection_field::commandLine)) {
            crashInfo.setCmd(getFieldValue(item));
        } else if (item.contains(collection_field::systemArch)) {
            crashInfo.setArch(getFieldValue(item));
        } else if (item.contains(collection_field::time)) {
            crashInfo.setTime(getFieldValue(item));
        } else if (item.contains(collection_field::packageName)) {
            crashInfo.setPackageName(getFieldValue(item));
        } else if (item.contains(collection_field::packageVersion)) {
            crashInfo.setPackageVersion(getFieldValue(item));
        } else if (item.contains(collection_field::packageDepend)) {
            setCurrentField(Field::PackageDepends);
        } else if (m_currentField == Field::PackageDepends) {
            packageDepends.append(item.toLocal8Bit());
            packageDepends.append('\n');
        } else if (item.contains(collection_field::stackTrace)) {
            setCurrentField(Field::StrackTrace);
            backTrace.append(item.toLocal8Bit());
            backTrace.append('\n');
        } else if ((m_currentField == Field::StrackTrace) && (item.at(0) == QChar('#'))) {
            backTrace.append('\t');
            backTrace.append(item.toLocal8Bit());
            backTrace.append('\n');
        } else if ((m_currentField == Field::StrackTrace) && (item.contains(collection_field::stackTraceThradTitle))) {
            backTrace.append('\n');
            backTrace.append(item.toLocal8Bit());
            backTrace.append('\n');
        }
    }

    crashInfo.setBackTrace(backTrace);
    crashInfo.setPackageDepends(packageDepends);

    infoFile.close();

    return true;
}

QByteArray Collection::getFieldValue(QString field)
{
    clearFieldJudge();

    if (field.isEmpty()) {
        return QByteArray("");
    }

    QList<QString> fieldList = field.split(':');
    if (field.size() >= 2) {
        return fieldList.at(1).toLocal8Bit();
    }

    return QByteArray("");
}

void Collection::clearFieldJudge(void)
{
    m_currentField = Field::Unknow;
    return;
}

void Collection::setCurrentField(Field field)
{
    clearFieldJudge();

    switch (field) {
    case Field::StrackTrace:
        m_currentField = Field::StrackTrace;
        return;
    case Field::PackageDepends:
        m_currentField = Field::PackageDepends;
        return;
    default:
        return;
    }
}

} // namespace crash_collection
